
package com.zoo.mongodb

import com.mongodb.MongoClient
import com.mongodb.BasicDBObject
import com.mongodb.MapReduceCommand
import com.mongodb.DBObject
import com.gmongo.GMongoClient

// Time excecution
def benchmark = { closure ->
    start = System.currentTimeMillis()
    closure.call()
    now = System.currentTimeMillis()
    now - start
}

// Print result
def printResults = { cur ->
    if(cur) {
        while(cur.hasNext()) {
            println cur.next()
        }
    }
}


// get database
def db = new GMongoClient("node01", 10001).getDB("testdb")
db.users.drop()

def tranCnt = 100
def duration = benchmark {
    for(id in 1..tranCnt) {
        def random = new Random()
        def rand = random.nextInt(50) + 25
        def addresses = []
        for(itemId in 1..5) {
            addresses << ['address1': (itemId + id) + " Test Street", 'city': "Wilmington", 'state': "DE", 'zip': "19042"]
        }
        def user = [
            'party_id':  id,
            'firstName': "Alex #" +id,
            'lastName':  "Tester",
            'addresses': addresses,
            'phones':    [home: "56225587" + rand, work: "30225587" + rand],
            'insert_ts': new Date()
        ]
        db.users << user
    }
}
// create index
db.users.createIndex(new BasicDBObject("party_id", 1))
db.users.createIndex(new BasicDBObject("addresses.address1", 1))
println "Created ${tranCnt} transactions in ${duration}ms (${duration / (tranCnt / 100)}ms per 100 insert)"


duration = benchmark {
    cur = db.users.find(party_id: 71)
}
println "[party_id: 71] Found #${cur.count()} users in ${duration} ms."
//printResults(cur)

duration = benchmark {
    cur = db.users.find(party_id: ['$gte': 90, '$lt': 95])
}
println "[party_id:['\$gte': 90, '\$lt': 95]] Found #${cur.count()} users in ${duration} ms."
//printResults(cur)

duration = benchmark {
    cur = db.users.find(firstName: ~/Alex #9[0-5]$.*/)
}
println "[firstName : Alex #9[0-5]] Found #${cur.count()} users in ${duration} ms."
//printResults(cur)

duration = benchmark {
    cur = db.users.find(["addresses.address1" : ~/^1.*$/] as BasicDBObject, ["addresses.address1", 1] as BasicDBObject)
}
println "addresses.address1 Found #${cur.count()} users in ${duration} ms."
//printResults(cur)

db.users.drop()

// test aggregate
println "add to users:"
db.users << [name: 'Jesse', age: 34]
db.users << [name: 'Jesse', age: 100]

cur = db.users.find()
printResults(cur)

println "find:"
db.users.aggregate(['$group': [_id: '$name', age: [$sum: '$age']]] as BasicDBObject)
/*
{ "result" : [ { "_id" : "Jesse", "age" : 134 } ], "ok" : 1 }
*/
db.users.aggregate(['$group': [_id: '$name', age: ['$push': '$age']]] as BasicDBObject)
/*
{
"result" : [
  {
    "_id" : "Jesse",
    "age" : [
      34,
      100]
  }
],
"ok" : 1
*/

db.users.drop()

// use gmongo http://mvnrepository.com/artifact/com.gmongo/gmongo
println "Reduce exam 1"

db.users <<  [name: 'Understanding JAVA', pages: 100]
db.users <<  [name: 'Understanding JSON', pages: 200]
db.users <<  [name: 'Understanding XML', pages: 300]
db.users <<  [name: 'Understanding Web Services', pages: 400]
db.users <<  [name: 'Understanding Axis2', pages: 150]

def map = """
    function() {
        var category;
        if( this.pages >= 250 )
            category = 'Big Books'
        else
            category = 'Small Books'
        emit(category, {name: this.name})
    }"""

def reduce = """
    function(key, values) {
        var sum = 0
        values.forEach(function(doc) {
            sum += 1
        });
        return {users: sum}
    }"""

//MapReduceCommand(DBCollection inputCollection, String map, String reduce, String outputCollection, MapReduceCommand.OutputType type, DBObject query)
//cmd = new MapReduceCommand(db, map, reduce, null, MapReduceCommand.OutputType.INLINE, null)//.INLINE(outputCollection=null) .MERGE(new result) .REDUCE(add result) .REPLACE
//cmd = new MapReduceCommand(users, map1, reduce1, "rmresult", MapReduceCommand.OutputType.REPLACE, null)
def cmd = new MapReduceCommand(db.users, map, reduce, null, MapReduceCommand.OutputType.INLINE, null)
def out = db.users.mapReduce(cmd)

for(DBObject o : out.results()) {
    println o.toString()
}

db.users.drop()

println "Reduce exam 2"
def words = ['foo', 'bar', 'baz']
def rand  = new Random()

db.words.drop()

duration = benchmark {
    100.times {
        db.words << [word: words[rand.nextInt(3)]]
    }
}
println "Insert #${db.words.count()} words in ${duration}ms."

def result = db.words.mapReduce(
    """
    function map() {
        emit(this.word, {count: 1})
    }
    """,
    """
    function reduce(key, values) {
        var count = 0
        for (var i = 0; i < values.length; i++)
            count += values[i].count
        return {count: count}
    }
    """,
    "mrresult",
    [:] // No Query
)

println "gdb.mrresult.count()=(3)=${db.mrresult.count()}"
println "gdb.mrresult.find()*.value*.count.sum()=(100)=${db.mrresult.find()*.value*.count.sum()}"

println "Group exam"

db.words.drop()

db.words.insert(day: 1, total: 10)
db.words.insert(day: 1, total: 14)
db.words.insert(day: 2, total: 45)
db.words.insert(day: 1, total:  9)
db.words.insert(day: 3, total: 32)
db.words.insert(day: 2, total: 11)
db.words.insert(day: 3, total: 34)

duration = benchmark {
    cur = db.words.group([day: true], [:], [count: 0], "function(doc, out) { out.count += doc.total }")
}
println "Insert #${cur} words in ${duration}ms."
// Will output [[day:1.0, count:33.0], [day:2.0, count:56.0], [day:3.0, count:66.0]]


println "Group exam with \$keyf"
db.clicks.drop()

db.clicks.insert(day: 1, total: 10)
db.clicks.insert(day: 1, total: 14)
db.clicks.insert(day: 2, total: 45)
db.clicks.insert(day: 1, total:  9)
db.clicks.insert(day: 3, total: 32)
db.clicks.insert(day: 2, total: 11)
db.clicks.insert(day: 3, total: 34)

def keyf = "function(clicks) { return clicks.day % 2 ? { odd: true } : { even: true } }"

def command = ['$keyf': keyf, cond: [:], initial: [count: 0], $reduce: "function(doc, out) { out.count += doc.total }"]

result = db.clicks.group(command)

// Will output [[odd:true, count:99.0], [even:true, count:56.0]]
println result

println "Aggregate exam"

db.zipcodes.drop()

duration = benchmark {
    db.zipcodes << [city: "ACMAR", loc: [-86.51557F, 33.584132F], pop: 6055, state: "AL", _id: "35004"]
    db.zipcodes << [city: "ADAMSVILLE", loc: [-86.959727F, 33.588437F], pop: 10616, state: "AL", _id: "35005"]
    db.zipcodes << [city: "ADGER", loc: [-87.167455F, 33.434277F], pop: 3205, state: "AL", _id: "35006"]
    db.zipcodes << [city: "KEYSTONE", loc: [-86.812861F, 33.236868F], pop: 14218, state: "AL", _id: "35007"]
    db.zipcodes << [city: "NEW SITE", loc: [-85.951086F, 32.941445F], pop: 19942, state: "AL", _id: "35010"]
}
println "Insert zipcode in ${duration}ms."

def aggrOutput
duration = benchmark {
    aggrOutput = db.zipcodes.aggregate(
        [ $project : [ city: 1, pop: 1 ] ],
        [ $match : [ pop: [ $gte : 10 * 1000 ] ] ],
        [ $sort: [ pop: -1] ]
    )
}
println "Aggregate #${aggrOutput.results().size()} words in ${duration}ms."

println "aggrOutput.results().size()=(3)=${aggrOutput.results().size()}"
println "aggrOutput.results()[0].city=(NEW SITE)=${aggrOutput.results()[0].city}"
println "aggrOutput.results()[1].city=(KEYSTONE)=${aggrOutput.results()[1].city}"
println "aggrOutput.results()[2].city=(ADAMSVILLE)=${aggrOutput.results()[2].city}"

println "Scripts exam"

db.command("function (x, y){ return x + y; }")

//db['system.js'] << [
//    _id: 'getNextId',
//    value: 'function(nm){ var ret = db.counters.findAndModify({query:{ _id:nm}, update:{$inc:{ seq:1}}, new:true, upsert: true}); return ret.seq;}'
//]

//db.loadServerScripts()

//cur = db['system.js'].find()
//println "find=${cur}"

println "Done!"
